#include "definitions.h"

static void TCC0_ClockInit (void)
{
    /* Selection of the Generator and write Lock for TCC0 TCC1 */
    GCLK_REGS->GCLK_CLKCTRL = GCLK_CLKCTRL_ID (GCLK_CLKCTRL_ID_TCC0_TCC1_Val) |
                              GCLK_CLKCTRL_GEN (GCLK_CLKCTRL_GEN_GCLK0_Val) |
                              GCLK_CLKCTRL_CLKEN_Msk;

    /* Configure the APBC Bridge Clocks */
    PM_REGS->PM_APBCMASK |= PM_APBCMASK_TCC0(1);
}

static void TCC0_PortInit (void)
{
    /************************** GROUP 1 Initialization *************************/
    PORT_REGS->GROUP[1].PORT_PINCFG[PIN_PB10F_TCC0_WO4 & 0x1F] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[1].PORT_PINCFG[PIN_PB11F_TCC0_WO5 & 0x1F] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[1].PORT_PINCFG[PIN_PB12F_TCC0_WO6 & 0x1F] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[1].PORT_PINCFG[PIN_PB13F_TCC0_WO7 & 0x1F] = PORT_PINCFG_PMUXEN (1);

    PORT_REGS->GROUP[1].PORT_PMUX[5] = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F; // PB10 TCC0_WO4, PB11 TCC0_WO5
    PORT_REGS->GROUP[1].PORT_PMUX[6] = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F; // PB12 TCC0_WO6, PB13 TCC0_WO7
}

/* Initialize TCC module */
void TCC0_PWM_Init (void)
{
    TCC0_ClockInit();
    TCC0_PortInit();

    /* Reset TCC */
    TCC0_REGS->TCC_CTRLA = TCC_CTRLA_SWRST_Msk;
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_SWRST_Msk)) {
        /* Wait for sync */
    }

    /* Clock prescaler */
    TCC0_REGS->TCC_CTRLA = TCC_CTRLA_PRESCALER_DIV2 ;
    TCC0_REGS->TCC_WEXCTRL = TCC_WEXCTRL_OTMX (0U);

    TCC0_REGS->TCC_WAVE = TCC_WAVE_WAVEGEN_NPWM;

    /* Configure duty cycle values */
    TCC0_REGS->TCC_CC[0] = 100U;
    TCC0_REGS->TCC_CC[1] = 400U;
    TCC0_REGS->TCC_CC[2] = 600U;
    TCC0_REGS->TCC_CC[3] = 1000U;
    TCC0_REGS->TCC_PER = 1999U;

    while (TCC0_REGS->TCC_SYNCBUSY) {
        /* Wait for sync */
    }
}

/* Start the PWM generation */
void TCC0_PWMStart (void)
{
    TCC0_REGS->TCC_CTRLA |= TCC_CTRLA_ENABLE_Msk;
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_ENABLE_Msk)) {
        /* Wait for sync */
    }
}

/* Stop the PWM generation */
void TCC0_PWMStop (void)
{
    TCC0_REGS->TCC_CTRLA &= ~TCC_CTRLA_ENABLE_Msk;
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_ENABLE_Msk)) {
        /* Wait for sync */
    }
}

/* Configure PWM period */
void TCC0_PWM24bitPeriodSet (uint32_t period)
{
    TCC0_REGS->TCC_PERB = period & 0xFFFFFF;
    while ((TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_PER_Msk)) == TCC_SYNCBUSY_PER_Msk) {
        /* Wait for sync */
    }
}

/* Read TCC period */
uint32_t TCC0_PWM24bitPeriodGet (void)
{
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_PER_Msk)) {
        /* Wait for sync */
    }
    return (TCC0_REGS->TCC_PER & 0xFFFFFF);
}

/* Configure dead time */
void TCC0_PWMDeadTimeSet (uint8_t deadtime_high, uint8_t deadtime_low)
{
    TCC0_REGS->TCC_WEXCTRL &= ~ (TCC_WEXCTRL_DTHS_Msk | TCC_WEXCTRL_DTLS_Msk);
    TCC0_REGS->TCC_WEXCTRL |= TCC_WEXCTRL_DTHS (deadtime_high) | TCC_WEXCTRL_DTLS (deadtime_low);
}

void TCC0_PWMPatternSet (uint8_t pattern_enable, uint8_t pattern_output)
{
    TCC0_REGS->TCC_PATTB = (uint16_t) (pattern_enable | (pattern_output << 8));
    while ((TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_PATT_Msk)) == TCC_SYNCBUSY_PATT_Msk) {
        /* Wait for sync */
    }
}

/* Set the counter*/
void TCC0_PWM24bitCounterSet (uint32_t count_value)
{
    TCC0_REGS->TCC_COUNT = count_value & 0xFFFFFF;
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_COUNT_Msk)) {
        /* Wait for sync */
    }
}

void TCC0_PWM24bitPulseSet (uint32_t channel, uint32_t pulse)
{
    TCC0_REGS->TCC_CCB[channel] = pulse & 0xFFFFFF;
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_PER_Msk)) {
        /* Wait for sync */
    }
}

uint32_t TCC0_PWM24bitPulseGet (uint32_t channel)
{
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_PER_Msk)) {
        /* Wait for sync */
    }
    return (TCC0_REGS->TCC_CCB[channel] & 0xFFFFFF);
}

/* Enable forced synchronous update */
void TCC0_PWMForceUpdate (void)
{
    TCC0_REGS->TCC_CTRLBSET |= TCC_CTRLBCLR_CMD_UPDATE;
    while (TCC0_REGS->TCC_SYNCBUSY & (TCC_SYNCBUSY_CTRLB_Msk)) {
        /* Wait for sync */
    }
}

/* Enable the period interrupt - overflow or underflow interrupt */
void TCC0_PWMPeriodInterruptEnable (void)
{
    TCC0_REGS->TCC_INTENSET = TCC_INTENSET_OVF_Msk;
}

/* Disable the period interrupt - overflow or underflow interrupt */
void TCC0_PWMPeriodInterruptDisable (void)
{
    TCC0_REGS->TCC_INTENCLR = TCC_INTENCLR_OVF_Msk;
}

/* Read interrupt flags */
uint32_t TCC0_PWMInterruptStatusGet (void)
{
    uint32_t interrupt_status;
    interrupt_status = TCC0_REGS->TCC_INTFLAG;
    /* Clear interrupt flags */
    TCC0_REGS->TCC_INTFLAG = interrupt_status;
    return interrupt_status;
}

#include <rtthread.h>

#if defined(RT_USING_DEVICE) && defined(RT_USING_PWM)

#include "rtdevice.h"

#define PWM_DEV_NAME        "pwm"   /* PWM 设备名称 */
struct rt_device_pwm samd21_pwm;    /* PWM 设备句柄 */

static rt_err_t drv_pwm_control (struct rt_device_pwm *device, int cmd, void *arg)
{
    struct rt_pwm_configuration *configuration = (struct rt_pwm_configuration *)arg;

    switch (cmd) {
        case PWM_CMD_ENABLE:
            TCC0_PWMStart();
            return RT_EOK;
        case PWM_CMD_DISABLE:
            TCC0_PWMStop();
            return RT_EOK;
        case PWM_CMD_SET:
            TCC0_PWM24bitPeriodSet (configuration->period);
            TCC0_PWM24bitPulseSet (configuration->channel, configuration->pulse);
            return RT_EOK;
        case PWM_CMD_GET:
            configuration->period = TCC0_PWM24bitPeriodGet();
            configuration->pulse = TCC0_PWM24bitPulseGet (configuration->channel);
            return RT_EOK;
        default:
            return RT_EINVAL;
    }
}

static struct rt_pwm_ops samd21_tcc0_ops = {
    drv_pwm_control,
};

static int samd21_rtt_pwm_init (void)
{
    int result = RT_EOK;

    /* pwm init */
    TCC0_PWM_Init();

    /* register pwm device */
    if (rt_device_pwm_register (&samd21_pwm, PWM_DEV_NAME, &samd21_tcc0_ops, RT_NULL) == RT_EOK) {
        rt_kprintf ("%s register success\n", PWM_DEV_NAME);
    } else {
        rt_kprintf ("%s register failed\n", PWM_DEV_NAME);
        result = -RT_ERROR;
    }

    return result;
}
INIT_DEVICE_EXPORT (samd21_rtt_pwm_init);

#endif
